﻿using System.Collections.Generic;
using NHibernate;
using NHibernate.Criterion;
using System;

using ScriptManager.Data.Persistence.Interface;

namespace ScriptManager.Data.Persistence.Repository
{
    public class RepositoryWithTypedId<T, I> : IRepositoryWithTypedId<T, I>, IDisposable
    {

        public RepositoryWithTypedId(ISessionFactory sessionFactory)
        {
            Session = sessionFactory.OpenSession();
        }

        protected virtual ISession Session { get; private set; }


        private void ClearSession()
        {
            if (StatelessSession)
                Session.Clear();
        }

        public virtual T Get(I id)
        {
            ClearSession();
            return Session.Get<T>(id);
        }

        public virtual List<T> GetAll()
        {
            ClearSession();
            ICriteria criteria = Session.CreateCriteria(typeof(T));
            return criteria.List<T>() as List<T>;
        }

        public virtual List<T> FindAll(IDictionary<string, object> propertyValuePairs)
        {
            ClearSession();
            ICriteria criteria = Session.CreateCriteria(typeof(T));

            foreach (string key in propertyValuePairs.Keys)
                criteria.Add(Expression.Eq(key, propertyValuePairs[key]));

            return criteria.List<T>() as List<T>;
        }

        public virtual List<T> FindAllByAssociation(string associate, I id)
        {
            ClearSession();
            ICriteria criteria = Session.CreateCriteria(typeof(T));
            criteria.CreateCriteria(associate).Add(Expression.Eq("ID", id));

            return criteria.List<T>() as List<T>;
        }

        public virtual T FindOne(IDictionary<string, object> propertyValuePairs)
        {
            List<T> foundList = FindAll(propertyValuePairs);

            if (foundList.Count > 1)
            {
                throw new NonUniqueResultException(foundList.Count);
            }
            else if (foundList.Count == 1)
            {
                return foundList[0];
            }

            return default(T);
        }

        public virtual void Delete(T entity)
        {
            Session.Delete(entity);
            ClearSession();
        }

        /// <summary>
        /// Although SaveOrUpdate _can_ be invoked to update an object with an assigned ID, you are 
        /// hereby forced instead to use Save/Update for better clarity.
        /// </summary>
        public virtual T SaveOrUpdate(T entity)
        {
            Session.SaveOrUpdate(entity);
            ClearSession();
            return entity;
        }

        #region IDisposable Members

        public void Dispose()
        {
            Session.Close();
        }

        #endregion

        public bool StatelessSession { get; set; }
    }
}
